﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace SimpleGameEngine
{
    /// <summary>
    /// Spatial hashing implementation for fast 2D collisions
    /// </summary>
    public class GameSpatialManager
    {
        private int SceneWidth;
        private int SceneHeight;
        private int Cols;
        private int Rows;
        private int CellSize;
        private Dictionary<int, List<GameObject>> Buckets;

        /// <summary>
        /// Initializes the buckets, each bucket will hold lists of game objects
        /// that fall within the same cell/bucket.
        /// Important: Always set the cellsize so that the largest gameobject
        /// in the game could fit in 4 cells of 2x2 arrangement.
        /// </summary>
        /// <param name="scenewidth"> The scene width. </param>
        /// <param name="sceneheight"> The scene height. </param>
        /// <param name="cellsize"> The desired cell size for each grid. </param>
        public void initialize(int scenewidth, int sceneheight, int cellsize)
        {
            Cols = scenewidth / cellsize;
            Rows = sceneheight / cellsize;
            SceneWidth = scenewidth;
            SceneHeight = sceneheight;
            CellSize = cellsize;

            // Buckets is a dictionary with initial capacity of Cols x Rows.
            // Key used are the bucket/cell/grid number.
            // Value used are List of GameObjects which fall on the same bucket/cell/grid.
            Buckets = new Dictionary<int, List<GameObject>>(Cols * Rows);

            for (int BucketIndex = 0; BucketIndex < Cols * Rows; BucketIndex++)
            {
                Buckets.Add(BucketIndex, new List<GameObject>());
            }
        }

        /// <summary>
        /// This will clear the buckets and initializes for re-use.
        /// </summary>
        internal void ClearBuckets()
        {
            //Buckets.Clear();
            for (int BucketIndex = 0; BucketIndex < Cols * Rows; BucketIndex++)
            {
                //Buckets.Add(BucketIndex, new List<GameObject>());
                Buckets[BucketIndex].Clear();
            }
        }

        /// <summary>
        /// This is a helper function to find out in what cell this point is in.
        /// Then, it will add this cell position in the list of cells.
        /// </summary>
        /// <param name="point"></param>
        /// <param name="Width"></param>
        /// <param name="buckettoaddto"></param>
        private void AddToBucket(PointF point, float Width, List<int> buckettoaddto)
        {
            int cellPosition = (int)(
                       (Math.Floor(point.X / CellSize)) +
                       (Math.Floor(point.Y / CellSize)) *
                       Width
            );
            if (!buckettoaddto.Contains(cellPosition))
                buckettoaddto.Add(cellPosition);

        }

        /// <summary>
        /// This will get the list of all the cells in which the 
        /// queried gameobject has occupied.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private List<int> GetBucketsForThisObj(GameObject obj)
        {
            List<int> bucketsObjIsIn = new List<int>();

            PointF min = new PointF(
                obj.Location.X - (obj.Radius),
                obj.Location.Y - (obj.Radius));
            PointF max = new PointF(
                obj.Location.X + (obj.Radius),
                obj.Location.Y + (obj.Radius));


            float width = SceneWidth / CellSize;
            //TopLeft
            AddToBucket(min, width, bucketsObjIsIn);
            //TopRight
            AddToBucket(new PointF(max.X, min.Y), width, bucketsObjIsIn);
            //BottomRight
            AddToBucket(new PointF(max.X, max.Y), width, bucketsObjIsIn);
            //BottomLeft
            AddToBucket(new PointF(min.X, max.Y), width, bucketsObjIsIn);

            return bucketsObjIsIn;
        }

        /// <summary>
        /// This will register the gameobject in each bucket it occupies.
        /// </summary>
        /// <param name="obj"></param>
        internal void RegisterObject(GameObject obj)
        {
            List<int> BucketNumberList = GetBucketsForThisObj(obj);
            foreach (var item in BucketNumberList)
            {
                Buckets[item].Add(obj);
            }
        }

        /// <summary>
        /// This will get a list of all gameobjects, where the queried
        /// gameobject occupies the same cells.
        /// If both objects occupied the same cell(s) then it is assumed
        /// they are near each other.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        internal List<GameObject> GetNearbyObjects(GameObject obj)
        {
            List<GameObject> objects = new List<GameObject>();
            List<int> BucketNumberList = GetBucketsForThisObj(obj);
            foreach (var item in BucketNumberList)
            {
                objects.AddRange(Buckets[item]);
            }
            return objects;
        }



    }
}
